<?php

	/* dependence */
	CoreProject::Using('core.abstracts');
	CoreProject::Using('core.log');

	/**
	 * @abstract
	 * @package		CoreProject
	 * @subpackage	Core
	 *
	 * cpr_Core_Socket
	 * cpr_Core_SocketConfig
	 *
	 */

	abstract class cpr_Core_Socket extends cpr_Core_ErrorsClass
	{
		/**
		 * @var	resource
		 */
		private $_socket;

		/**
		 * @var	cpr_Core_SocketConfig
		 */
		private $_config;

		/**
		 * @var	string
		 */
		private $_lastBuffer = '';

		/**
		 * @param	cpr_Core_SocketConfig	$config
		 * @return	cpr_Core_Socket
		 */
		public function cpr_Core_Socket($_config)
		{
			$this->_config = $_config;
		}

		/**
		 * @param	string	$_class = null
		 * @return	bool
		 */
		public function Connect($_class = null)
		{
			$_class = $this->_classPreparation($_class, 'socket '.__FUNCTION__.'()');
			if ($this->IsConnect())
			{
				$this->setError($_class, 'connection also avalible.');
				$this->cleanUp();
				return false;
			}

			if (null === $this->_config)
			{
				$this->setError($_class, 'socket config is null.');
				$this->cleanUp();
				return false;
			}

			$_errorNo = 0;
			$_errorStr = '';

			cpr_Core_Log::Write($_class.': start connect to '.$this->_config->Host().':'.$this->_config->Port());
			$this->_socket = @fsockopen($this->_config->Host(), $this->_config->Port(), $_errorNo, $_errorStr, $this->_config->ConnectTimeOut());
			if (false === $this->_socket || null === $this->_socket)
			{
				$this->setError($_class, 'can\'t connect to server '.$this->_config->Host().':'. $this->_config->Port().'.');
				$this->setError($_class, $_errorStr.' ('.$_errorStr.').');
				$this->cleanUp();
				return false;
			}

			@socket_set_timeout($this->_socket, $this->_config->SocketTimeOut());
			@socket_set_blocking($this->_socket, true);

			return true;
		}

		/**
		 * @return	true
		 */
		public function Disconnect()
		{
			return $this->cleanUp();
		}

		/**
		* @return	bool
		*/
		public function IsConnect()
		{
			return is_resource($this->_socket);
		}

		/**
		 * @return	string
		 */
		public function LastBuffer()
		{
			return trim($this->_lastBuffer);
		}

		/**
		 * @param	string	$_str
		 * @param	string	$_class = null
		 * @return	bool
		 */
		protected function putLine($_str, $_class = null)
		{
			if (!$this->IsConnect())
			{
				return false;
			}

			$this->_resetTimeOut();

			$_class = $this->_classPreparation($_class, 'socket '.__FUNCTION__.'()');
			$_str = trim($_str, CPR_CRLF).CPR_CRLF;
			cpr_Core_Log::Write($_class.': > '.cpr_Core_Utils::ShowCrLf($_str));
			if (!@fwrite($this->_socket, $_str, strlen($_str)))
			{
				$this->setError($_class, 'error while send "'.$_str.'".');
				$this->cleanUp();
				return false;
			}

			return true;
		}

		/**
		 * @param	string	$_class = null
		 * @param	bool	$isLog = true
		 * @return	string|false
		 */
		protected function getLine($_class = null, $isLog = true)
		{
			if (!$this->IsConnect())
			{
				return false;
			}

			$this->_resetTimeOut();

			$_class = $this->_classPreparation($_class, 'socket '.__FUNCTION__.'()');
			$this->_lastBuffer = @fgets($this->_socket, 128);
			if ($isLog)
			{
				cpr_Core_Log::Write($_class.': < '.cpr_Core_Utils::ShowCrLf($this->_lastBuffer));
			}

			if (!$this->_lastBuffer)
			{
				if (false === $this->_lastBuffer)
				{
					$this->setError($_class, 'fgets response is false.');
					$this->cleanUp();
				}
				else if (strlen($this->_lastBuffer) == 0)
				{
					return $this->_lastBuffer;
				}
				else
				{
					$_socket_status = @socket_get_status($this->_socket);
					if (isset($_socket_status['timed_out']) && $_socket_status['timed_out'])
					{
						$this->setError($_class, 'socket timeout reached during connection.');
						$this->cleanUp();
					}
				}
			}

			return $this->_lastBuffer;
		}

		/**
		 * @return	bool
		 */
		protected function cleanUp()
		{
			if ($this->IsConnect())
			{
				@fclose($this->_socket);
			}
			$this->_socket = null;
			return true;
		}


		/**
		 * @param	string	$_class
		 * @param	string	$_defaultClass
		 * @return	string
		 */
		private function _classPreparation($_class, $_defaultClass)
		{
			return (null === $_class) ? $_defaultClass : $_class;
		}

		private function _resetTimeOut()
		{
			static $_time_to_update = null;
			$_time = time();
			if ($_time_to_update < $_time)
			{
				@set_time_limit(30);
				$_time_to_update = $_time + 20;
			}
		}
	}

	final class cpr_Core_SocketConfig extends cpr_Core_ErrorsClass
	{
		/**
		 * @var	string
		 */
		private $_host;

		/**
		 * @var	int
		 */
		private $_port;

		/**
		 * @var	int
		 */
		private $_connectTimeOut;

		/**
		 * @var	int
		 */
		private $_socketTimeOut;

		/*
		 * @var bool
		 */
		private $_ssl = false;

		/**
		 * @param	string	$host
		 * @param	int		$port
		 * @param	bool	$ssl = false
		 * @param	int		$connectTimeOut = 10
		 * @param	int		$socketTimeOut = 10
		 * @return	cpr_Core_SocketConfig
		 */
		public function __construct($host, $port, $ssl = false, $connectTimeOut = 10 , $socketTimeOut = 10)
		{
			if (!trim($host))
			{
				$this->setError('socket config', 'please give a server address.');
			}

			if ($port < 1 && $port > 65535 || !trim($port))
			{
				$this->setError('socket config', 'port not set or out of range (1 - 65535).');
			}

			if ($connectTimeOut < 0 && $connectTimeOut > 25 || !trim($connectTimeOut))
			{
				$this->setError('socket config', 'connection timeout not set or out of range (0 - 25).');
			}

			if ($socketTimeOut < 0 && $socketTimeOut > 25 || !trim($socketTimeOut))
			{
				$this->setError('socket config', 'socket timeout not set or out of range (0 - 25).');
			}

			if ($ssl && !function_exists('openssl_open'))
			{
				$this->setError('socket config', 'ssl not work, function "openssl_open" dosn\'t exist.');
			}

			if ($ssl && strtolower(substr($host, 0, 6)) !== 'ssl://')
			{
				$this->_ssl = true;
				$host = 'ssl://'.$host;
			}

			$this->_host = $host;
			$this->_port = (int) $port;
			$this->_connectTimeOut = (int) $connectTimeOut;
			$this->_socketTimeOut = (int) $socketTimeOut;
		}

		/**
		 * @return	string
		 */
		public function Host()
		{
			return $this->_host;
		}

		/**
		 * @return	int
		 */
		public function Port()
		{
			return $this->_port;
		}

		/**
		 * @return	int
		 */
		public function ConnectTimeOut()
		{
			return $this->_connectTimeOut;
		}

		/**
		 * @return	int
		 */
		public function SocketTimeOut()
		{
			return $this->_socketTimeOut;
		}

		/**
		 * @return bool
		 */
		public function IsSsl()
		{
			return $this->_ssl;
		}
	}
